#ifndef _STORAGE_KVSTORAGE_
#define _STORAGE_KVSTORAGE_

#include <sstream>

#include <db.h>

#include <pthread.h>

namespace Storage
{
  template <typename K,typename V>
    class KVStorage
    {
      DB *dbp;
      size_t req;

      static DB_ENV* envp;

      static pthread_mutex_t mutex;
      public:

      KVStorage(const std::string& fileName,const std::string& tableName)
      {
        int r;
        pthread_mutex_lock( &mutex );

        if ( envp == NULL )
        {
          r = db_env_create(&envp, 0);
          if ( r != 0 ) 
            std::cerr << "Error creataing environment handle" << db_strerror(r) << std::endl;
            
          r = envp->open(envp, "./databases", DB_CREATE | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL, 0);
          if (r != 0 )  
            std::cerr << "Error opening environment" <<  db_strerror(r) << std::endl;



        }

        r = db_create(&dbp,envp,0);
        if ( r != 0 )
          std::cerr << "Failed db_create" << __FILE__ << __LINE__ << std::endl;

        r = dbp->open(dbp,NULL,fileName.c_str(),tableName.c_str(),DB_BTREE,DB_CREATE | DB_AUTO_COMMIT,0);

        if (r != 0 )
          std::cerr << "Failed dbp->open" << __FILE__ << __LINE__ << std::endl;

        pthread_mutex_unlock( &mutex );

        req = 0;
      };

      int Put(const K& key,V& value)
      {
        std::ostringstream sKey,sValue;
        DBT dbKey,dbValue;

        memset(&dbKey,0,sizeof(dbKey));
        memset(&dbValue,0,sizeof(dbValue));

        sKey << key;
        sValue << value;       

        size_t lengthKey = sKey.str().length() + 1;
        size_t lengthValue = sValue.str().length() + 1;

        char* rawKey = new char[lengthKey];
        memcpy(rawKey,sKey.str().c_str(),lengthKey);

        char* rawValue = new char[lengthValue];
        memcpy(rawValue,sValue.str().c_str(),lengthValue);

        dbKey.data = rawKey;
        dbKey.size = lengthKey;

        dbValue.data = rawValue;
        dbValue.size = lengthValue;

        int r = dbp->put(dbp,NULL,&dbKey,&dbValue,DB_NOOVERWRITE);

        delete[] rawKey;
        delete[] rawValue;


        req++;
        if (req == 5)
        {
          dbp->sync(dbp,0);
          req = 0;
        }


        if ( r == DB_KEYEXIST )
          return 1;
        else if (r != 0)
          return 2;
        return 0;

      };

      V Get(const K& key)
      {
        std::ostringstream sKey;

        DBT dbKey,dbValue;
        V value;

        memset(&dbKey,0,sizeof(dbKey));
        memset(&dbValue,0,sizeof(dbValue));

        sKey << key;

        size_t lengthKey = sKey.str().length() + 1;

        char* rawKey = new char[lengthKey];
        memcpy(rawKey,sKey.str().c_str(),lengthKey);

        dbKey.data = rawKey;
        dbKey.size = lengthKey;

        int r = dbp->get(dbp,NULL,&dbKey,&dbValue,0);
        if ( r == DB_NOTFOUND)
          throw KeyNotFoundInDb();
        else if ( r != 0)
          throw DbError();


        std::istringstream sValue((char*)dbValue.data);
        sValue >> value;
        delete[] rawKey;
        return value;
      };

      void Delete(const K& key)
      {
        std::ostringstream sKey;

        DBT dbKey;

        memset(&dbKey,0,sizeof(dbKey));

        sKey << key;

        size_t lengthKey = sKey.str().length() + 1;

        char* rawKey = new char[lengthKey];
        memcpy(rawKey,sKey.str().c_str(),lengthKey);

        dbKey.data = rawKey;
        dbKey.size = lengthKey;

        int r = dbp->del(dbp,NULL,&dbKey,0);
        if ( r == DB_NOTFOUND)
          throw KeyNotFoundInDb();
        else if ( r != 0)
          throw DbError();

        delete[] rawKey;
      };

      void GetAllKeys(std::vector<K>& v)
      {
        DBC* cursor;
        dbp->cursor(dbp,NULL,&cursor,0);
        DBT dbKey,dbValue;

        memset(&dbKey,0,sizeof(dbKey));
        memset(&dbValue,0,sizeof(dbValue));
        int r;
        while ( ( r = cursor->get(cursor,&dbKey,&dbValue,DB_NEXT)) == 0)
        {
          K key;
          std::istringstream sKey((char*)dbKey.data);
          sKey >> key;
          v.push_back(key);
        }

        cursor->close(cursor);
      };

      ~KVStorage()
      {
        dbp->close(dbp,0);
      }
    };

  template <typename K,typename V>
    pthread_mutex_t KVStorage<K,V>::mutex = PTHREAD_MUTEX_INITIALIZER;

  template <typename K,typename V>
    DB_ENV* KVStorage<K,V>::envp = NULL;
};
#endif
